/*
 *  linux/kernel/console.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 *	console.c
 *
 * This module implements the console io functions
 *	'void con_init(void)'
 *	'void con_write(struct tty_queue * queue)'
 * Hopefully this will be a rather complete VT102 implementation.
 *
 * Beeping thanks to John T Kohl.
 */

/*
 *  NOTE!!! We sometimes disable and enable interrupts for a short while
 * (to put a word in video IO), but this will work even for keyboard
 * interrupts. We know interrupts aren't enabled when getting a keyboard
 * interrupt, as we use trap-gates. Hopefully all is well.
 */

/*
 * Code to check for different video-cards mostly by Galen Hunt,
 * <g-hunt@ee.utah.edu>
 */

#include <linux/sched.h>
#include <linux/tty.h>
#include <asm/io.h>
#include <asm/system.h>

/*
 * These are set up by the setup-routine at boot-time:
 */

#define ORIG_X            (*(unsigned char *)0x90000)
#define ORIG_Y            (*(unsigned char *)0x90001)
#define ORIG_VIDEO_PAGE   (*(unsigned short *)0x90004)
#define ORIG_VIDEO_MODE   ((*(unsigned short *)0x90006) & 0xff)
#define ORIG_VIDEO_COLS   (((*(unsigned short *)0x90006) & 0xff00) >> 8)
#define ORIG_VIDEO_LINES  (25)
#define ORIG_VIDEO_EGA_AX (*(unsigned short *)0x90008)
#define ORIG_VIDEO_EGA_BX (*(unsigned short *)0x9000a)
#define ORIG_VIDEO_EGA_CX (*(unsigned short *)0x9000c)

#define VIDEO_TYPE_MDA    0x10 /* Monochrome Text Display	*/
#define VIDEO_TYPE_CGA    0x11 /* CGA Display 			*/
#define VIDEO_TYPE_EGAM   0x20 /* EGA/VGA in Monochrome Mode	*/
#define VIDEO_TYPE_EGAC   0x21 /* EGA/VGA in Color Mode	*/

#define NPAR              16

extern void keyboard_interrupt(void);

static unsigned char video_type;        /* Type of display being used	*/
static unsigned long video_num_columns; /* Number of text columns	*/
static unsigned long video_size_row;    /* Bytes per row		*/
static unsigned long video_num_lines;   /* Number of test lines		*/
static unsigned char video_page;        /* Initial video page		*/
static unsigned long video_mem_start;   /* Start of video RAM		*/
static unsigned long video_mem_end;     /* End of video RAM (sort of)	*/
static unsigned short video_port_reg;   /* Video register select port	*/
static unsigned short video_port_val;   /* Video register value port	*/
static unsigned short video_erase_char; /* Char+Attrib to erase with	*/

static unsigned long origin;            /* Used for EGA/VGA fast scroll	*/
static unsigned long scr_end;           /* Used for EGA/VGA fast scroll	*/
static unsigned long pos;
static unsigned long x, y;
static unsigned long top, bottom;
static unsigned long state = 0;
static unsigned long npar, par[NPAR];
static unsigned long ques = 0;
static unsigned char attr = 0x07;

static void sysbeep(void);

/*
 * this is what the terminal answers to a ESC-Z or csi0c
 * query (= vt100 response).
 */
#define RESPONSE "\033[?1;2c"

/* NOTE! gotoxy thinks x==video_num_columns is ok */
static inline void gotoxy(unsigned int new_x, unsigned int new_y) {
    if (new_x > video_num_columns || new_y >= video_num_lines)
        return;
    x = new_x;
    y = new_y;
    pos = origin + y * video_size_row + (x << 1);
}

static inline void set_origin(void) {
    cli();
    outb_p(12, video_port_reg);
    outb_p(0xff & ((origin - video_mem_start) >> 9), video_port_val);
    outb_p(13, video_port_reg);
    outb_p(0xff & ((origin - video_mem_start) >> 1), video_port_val);
    sti();
}

static void scrup(void) {
    if (video_type == VIDEO_TYPE_EGAC || video_type == VIDEO_TYPE_EGAM) {
        if (!top && bottom == video_num_lines) {
            origin += video_size_row;
            pos += video_size_row;
            scr_end += video_size_row;
            if (scr_end > video_mem_end) {
                __asm__("cld\n\t"
                        "rep\n\t"
                        "movsl\n\t"
                        "movl %2,%1\n\t"
                        "rep\n\t"
                        "stosw" ::"a"(video_erase_char),
                        "c"((video_num_lines - 1) * video_num_columns >> 1),
                        "m"(video_num_columns),
                        "D"(video_mem_start),
                        "S"(origin));
                scr_end -= origin - video_mem_start;
                pos -= origin - video_mem_start;
                origin = video_mem_start;
            } else {
                __asm__("cld\n\t"
                        "rep\n\t"
                        "stosw" ::"a"(video_erase_char),
                        "c"(video_num_columns),
                        "D"(scr_end - video_size_row));
            }
            set_origin();
        } else {
            __asm__("cld\n\t"
                    "rep\n\t"
                    "movsl\n\t"
                    "movl %2,%%ecx\n\t"
                    "rep\n\t"
                    "stosw" ::"a"(video_erase_char),
                    "c"((bottom - top - 1) * video_num_columns >> 1),
                    "m"(video_num_columns),
                    "D"(origin + video_size_row * top),
                    "S"(origin + video_size_row * (top + 1)));
        }
    } else /* Not EGA/VGA */
    {
        __asm__("cld\n\t"
                "rep\n\t"
                "movsl\n\t"
                "movl %2,%%ecx\n\t"
                "rep\n\t"
                "stosw" ::"a"(video_erase_char),
                "c"((bottom - top - 1) * video_num_columns >> 1),
                "m"(video_num_columns),
                "D"(origin + video_size_row * top),
                "S"(origin + video_size_row * (top + 1)));
    }
}

static void scrdown(void) {
    if (video_type == VIDEO_TYPE_EGAC || video_type == VIDEO_TYPE_EGAM) {
        __asm__("std\n\t"
                "rep\n\t"
                "movsl\n\t"
                "addl $2,%%edi\n\t" /* %edi has been decremented by 4 */
                "movl %2,%%ecx\n\t"
                "rep\n\t"
                "stosw" ::"a"(video_erase_char),
                "c"((bottom - top - 1) * video_num_columns >> 1),
                "m"(video_num_columns),
                "D"(origin + video_size_row * bottom - 4),
                "S"(origin + video_size_row * (bottom - 1) - 4));
    } else /* Not EGA/VGA */
    {
        __asm__("std\n\t"
                "rep\n\t"
                "movsl\n\t"
                "addl $2,%%edi\n\t" /* %edi has been decremented by 4 */
                "movl %2,%%ecx\n\t"
                "rep\n\t"
                "stosw" ::"a"(video_erase_char),
                "c"((bottom - top - 1) * video_num_columns >> 1),
                "m"(video_num_columns),
                "D"(origin + video_size_row * bottom - 4),
                "S"(origin + video_size_row * (bottom - 1) - 4));
    }
}

static void lf(void) {
    if (y + 1 < bottom) {
        y++;
        pos += video_size_row;
        return;
    }
    scrup();
}

static void ri(void) {
    if (y > top) {
        y--;
        pos -= video_size_row;
        return;
    }
    scrdown();
}

static void cr(void) {
    pos -= x << 1;
    x = 0;
}

static void del(void) {
    if (x) {
        pos -= 2;
        x--;
        *(unsigned short *)pos = video_erase_char;
    }
}

static void csi_J(int par) {
    long count;
    long start;

    switch (par) {
    case 0: /* erase from cursor to end of display */
        count = (scr_end - pos) >> 1;
        start = pos;
        break;
    case 1: /* erase from start to cursor */
        count = (pos - origin) >> 1;
        start = origin;
        break;
    case 2: /* erase whole display */
        count = video_num_columns * video_num_lines;
        start = origin;
        break;
    default:
        return;
    }
    __asm__("cld\n\t"
            "rep\n\t"
            "stosw\n\t" ::"c"(count),
            "D"(start), "a"(video_erase_char));
}

static void csi_K(int par) {
    long count;
    long start;

    switch (par) {
    case 0: /* erase from cursor to end of line */
        if (x >= video_num_columns)
            return;
        count = video_num_columns - x;
        start = pos;
        break;
    case 1: /* erase from start of line to cursor */
        start = pos - (x << 1);
        count = (x < video_num_columns) ? x : video_num_columns;
        break;
    case 2: /* erase whole line */
        start = pos - (x << 1);
        count = video_num_columns;
        break;
    default:
        return;
    }
    __asm__("cld\n\t"
            "rep\n\t"
            "stosw\n\t" ::"c"(count),
            "D"(start), "a"(video_erase_char));
}

void csi_m(void) {
    int i;

    for (i = 0; i <= npar; i++)
        switch (par[i]) {
        case 0:
            attr = 0x07;
            break;
        case 1:
            attr = 0x0f;
            break;
        case 4:
            attr = 0x0f;
            break;
        case 7:
            attr = 0x70;
            break;
        case 27:
            attr = 0x07;
            break;
        }
}

static inline void set_cursor(void) {
    cli();
    outb_p(14, video_port_reg);
    outb_p(0xff & ((pos - video_mem_start) >> 9), video_port_val);
    outb_p(15, video_port_reg);
    outb_p(0xff & ((pos - video_mem_start) >> 1), video_port_val);
    sti();
}

static void respond(struct tty_struct *tty) {
    char *p = RESPONSE;

    cli();
    while (*p) {
        PUTCH(*p, tty->read_q);
        p++;
    }
    sti();
    copy_to_cooked(tty);
}

static void insert_char(void) {
    int i = x;
    unsigned short tmp, old = video_erase_char;
    unsigned short *p = (unsigned short *)pos;

    while (i++ < video_num_columns) {
        tmp = *p;
        *p = old;
        old = tmp;
        p++;
    }
}

static void insert_line(void) {
    int oldtop, oldbottom;

    oldtop = top;
    oldbottom = bottom;
    top = y;
    bottom = video_num_lines;
    scrdown();
    top = oldtop;
    bottom = oldbottom;
}

static void delete_char(void) {
    int i;
    unsigned short *p = (unsigned short *)pos;

    if (x >= video_num_columns)
        return;
    i = x;
    while (++i < video_num_columns) {
        *p = *(p + 1);
        p++;
    }
    *p = video_erase_char;
}

static void delete_line(void) {
    int oldtop, oldbottom;

    oldtop = top;
    oldbottom = bottom;
    top = y;
    bottom = video_num_lines;
    scrup();
    top = oldtop;
    bottom = oldbottom;
}

static void csi_at(unsigned int nr) {
    if (nr > video_num_columns)
        nr = video_num_columns;
    else if (!nr)
        nr = 1;
    while (nr--)
        insert_char();
}

static void csi_L(unsigned int nr) {
    if (nr > video_num_lines)
        nr = video_num_lines;
    else if (!nr)
        nr = 1;
    while (nr--)
        insert_line();
}

static void csi_P(unsigned int nr) {
    if (nr > video_num_columns)
        nr = video_num_columns;
    else if (!nr)
        nr = 1;
    while (nr--)
        delete_char();
}

static void csi_M(unsigned int nr) {
    if (nr > video_num_lines)
        nr = video_num_lines;
    else if (!nr)
        nr = 1;
    while (nr--)
        delete_line();
}

static int saved_x = 0;
static int saved_y = 0;

static void save_cur(void) {
    saved_x = x;
    saved_y = y;
}

static void restore_cur(void) {
    gotoxy(saved_x, saved_y);
}

void con_write(struct tty_struct *tty) {
    int nr;
    char c;

    nr = CHARS(tty->write_q);
    while (nr--) {
        GETCH(tty->write_q, c);
        switch (state) {
        case 0:
            if (c > 31 && c < 127) {
                if (x >= video_num_columns) {
                    x -= video_num_columns;
                    pos -= video_size_row;
                    lf();
                }
                __asm__("movb %1,%%ah\n\t"
                        "movw %%ax,%2\n\t" ::"a"(c),
                        "m"(attr), "m"(*(short *)pos));
                pos += 2;
                x++;
            } else if (c == 27)
                state = 1;
            else if (c == 10 || c == 11 || c == 12)
                lf();
            else if (c == 13)
                cr();
            else if (c == ERASE_CHAR(tty))
                del();
            else if (c == 8) {
                if (x) {
                    x--;
                    pos -= 2;
                }
            } else if (c == 9) {
                c = 8 - (x & 7);
                x += c;
                pos += c << 1;
                if (x > video_num_columns) {
                    x -= video_num_columns;
                    pos -= video_size_row;
                    lf();
                }
                c = 9;
            } else if (c == 7)
                sysbeep();
            break;
        case 1:
            state = 0;
            if (c == '[')
                state = 2;
            else if (c == 'E')
                gotoxy(0, y + 1);
            else if (c == 'M')
                ri();
            else if (c == 'D')
                lf();
            else if (c == 'Z')
                respond(tty);
            else if (x == '7')
                save_cur();
            else if (x == '8')
                restore_cur();
            break;
        case 2:
            for (npar = 0; npar < NPAR; npar++)
                par[npar] = 0;
            npar = 0;
            state = 3;
            if ((ques = (c == '?')))
                break;
        case 3:
            if (c == ';' && npar < NPAR - 1) {
                npar++;
                break;
            } else if (c >= '0' && c <= '9') {
                par[npar] = 10 * par[npar] + c - '0';
                break;
            } else
                state = 4;
        case 4:
            state = 0;
            switch (c) {
            case 'G':
            case '`':
                if (par[0])
                    par[0]--;
                gotoxy(par[0], y);
                break;
            case 'A':
                if (!par[0])
                    par[0]++;
                gotoxy(x, y - par[0]);
                break;
            case 'B':
            case 'e':
                if (!par[0])
                    par[0]++;
                gotoxy(x, y + par[0]);
                break;
            case 'C':
            case 'a':
                if (!par[0])
                    par[0]++;
                gotoxy(x + par[0], y);
                break;
            case 'D':
                if (!par[0])
                    par[0]++;
                gotoxy(x - par[0], y);
                break;
            case 'E':
                if (!par[0])
                    par[0]++;
                gotoxy(0, y + par[0]);
                break;
            case 'F':
                if (!par[0])
                    par[0]++;
                gotoxy(0, y - par[0]);
                break;
            case 'd':
                if (par[0])
                    par[0]--;
                gotoxy(x, par[0]);
                break;
            case 'H':
            case 'f':
                if (par[0])
                    par[0]--;
                if (par[1])
                    par[1]--;
                gotoxy(par[1], par[0]);
                break;
            case 'J':
                csi_J(par[0]);
                break;
            case 'K':
                csi_K(par[0]);
                break;
            case 'L':
                csi_L(par[0]);
                break;
            case 'M':
                csi_M(par[0]);
                break;
            case 'P':
                csi_P(par[0]);
                break;
            case '@':
                csi_at(par[0]);
                break;
            case 'm':
                csi_m();
                break;
            case 'r':
                if (par[0])
                    par[0]--;
                if (!par[1])
                    par[1] = video_num_lines;
                if (par[0] < par[1] &&
                    par[1] <= video_num_lines) {
                    top = par[0];
                    bottom = par[1];
                }
                break;
            case 's':
                save_cur();
                break;
            case 'u':
                restore_cur();
                break;
            }
        }
    }
    set_cursor();
}

/*
 *  void con_init(void);
 *
 * This routine initalizes console interrupts, and does nothing
 * else. If you want the screen to clear, call tty_write with
 * the appropriate escape-sequece.
 *
 * Reads the information preserved by setup.s to determine the current display
 * type and sets everything accordingly.
 */
void con_init(void) {
    register unsigned char a;
    char *display_desc = "????";
    char *display_ptr;

    video_num_columns = ORIG_VIDEO_COLS;
    video_size_row = video_num_columns * 2;
    video_num_lines = ORIG_VIDEO_LINES;
    video_page = ORIG_VIDEO_PAGE;
    video_erase_char = 0x0720;

    if (ORIG_VIDEO_MODE == 7) /* Is this a monochrome display? */
    {
        video_mem_start = 0xb0000;
        video_port_reg = 0x3b4;
        video_port_val = 0x3b5;
        if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) {
            video_type = VIDEO_TYPE_EGAM;
            video_mem_end = 0xb8000;
            display_desc = "EGAm";
        } else {
            video_type = VIDEO_TYPE_MDA;
            video_mem_end = 0xb2000;
            display_desc = "*MDA";
        }
    } else /* If not, it is color. */
    {
        video_mem_start = 0xb8000;
        video_port_reg = 0x3d4;
        video_port_val = 0x3d5;
        if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) {
            video_type = VIDEO_TYPE_EGAC;
            video_mem_end = 0xbc000;
            display_desc = "EGAc";
        } else {
            video_type = VIDEO_TYPE_CGA;
            video_mem_end = 0xba000;
            display_desc = "*CGA";
        }
    }

    /* Let the user known what kind of display driver we are using */

    display_ptr = ((char *)video_mem_start) + video_size_row - 8;
    while (*display_desc) {
        *display_ptr++ = *display_desc++;
        display_ptr++;
    }

    /* Initialize the variables used for scrolling (mostly EGA/VGA)	*/

    origin = video_mem_start;
    scr_end = video_mem_start + video_num_lines * video_size_row;
    top = 0;
    bottom = video_num_lines;

    gotoxy(ORIG_X, ORIG_Y);
    set_trap_gate(0x21, &keyboard_interrupt);
    outb_p(inb_p(0x21) & 0xfd, 0x21);
    a = inb_p(0x61);
    outb_p(a | 0x80, 0x61);
    outb(a, 0x61);
}
/* from bsd-net-2: */

void sysbeepstop(void) {
    /* disable counter 2 */
    outb(inb_p(0x61) & 0xFC, 0x61);
}

int beepcount = 0;

static void sysbeep(void) {
    /* enable counter 2 */
    outb_p(inb_p(0x61) | 3, 0x61);
    /* set command for counter 2, 2 byte write */
    outb_p(0xB6, 0x43);
    /* send 0x637 for 750 HZ */
    outb_p(0x37, 0x42);
    outb(0x06, 0x42);
    /* 1/8 second */
    beepcount = HZ / 8;
}
